﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Text;
using System.Web.Services.Description;
using System.Net;
using System.IO;

namespace WSDLParser
{
    /// <summary>
    /// Information about a web service
    /// </summary>
    /// 
    public class Parser
    {
        WebMethodInfoCollection _webMethods = new WebMethodInfoCollection();
        Uri _url;

        /// <summary>
        /// Constructor creates the web service info from the given url.
        /// </summary>
        /// <param name="url">
        public Parser(Uri url)
        {
            if (url == null)
                throw new ArgumentNullException("url");
            _url = url;
            _webMethods = GetWebServiceDescription(url);
        }

        public Parser(Stream stream)
        {
            _webMethods = GetWebServiceDescription(stream);
        }

        /// <summary>
        /// Load the WSDL file from the given url.
        /// Use the ServiceDescription class to walk the wsdl and create the WebServiceInfo
        /// instance.
        /// </summary>
        /// <param name="url">
        private WebMethodInfoCollection GetWebServiceDescription(Uri url)
        {
            UriBuilder uriBuilder = new UriBuilder(url);
            uriBuilder.Query = "WSDL";

            WebMethodInfoCollection webMethodInfos = new WebMethodInfoCollection();

            HttpWebRequest webRequest = (HttpWebRequest)WebRequest.Create(uriBuilder.Uri);
            webRequest.ContentType = "text/xml;charset=\"utf-8\"";
            webRequest.Method = "GET";
            webRequest.Accept = "text/xml";

            using (System.Net.WebResponse response = webRequest.GetResponse())
            using (System.IO.Stream stream = response.GetResponseStream())
            {
                return GetWebServiceDescription(stream);
            }
        }

        private WebMethodInfoCollection GetWebServiceDescription(Stream stream)
        {
            ServiceDescription serviceDescription;
            WebMethodInfoCollection webMethodInfos = new WebMethodInfoCollection();

            serviceDescription = ServiceDescription.Read(stream);

            foreach (PortType portType in serviceDescription.PortTypes)
            {
                foreach (Operation operation in portType.Operations)
                {
                    string operationName = operation.Name;
                    string inputMessageName = operation.Messages.Input.Message.Name;
                    string outputMessageName = operation.Messages.Output.Message.Name;

                    // get the message part
                    string inputMessagePartName = serviceDescription.Messages[inputMessageName].Parts[0].Element.Name;
                    string outputMessagePartName = serviceDescription.Messages[outputMessageName].Parts[0].Element.Name;

                    // get the parameter name and type
                    Parameter[] inputParameters = GetParameters(serviceDescription, inputMessagePartName);
                    Parameter[] outputParameters = GetParameters(serviceDescription, outputMessagePartName);

                    WebMethodInfo webMethodInfo = new WebMethodInfo(operation.Name, inputParameters, outputParameters);

                    webMethodInfos.Add(webMethodInfo);
                }
            }

            return webMethodInfos;
        }

        /// <summary>
        /// Walk the schema definition to find the parameters of the given message.
        /// </summary>
        /// <param name="serviceDescription">
        /// <param name="messagePartName">
        /// <returns></returns>
        private static Parameter[] GetParameters(ServiceDescription serviceDescription, string messagePartName)
        {
            List<Parameter> parameters = new List<Parameter>();

            Types types = serviceDescription.Types;
            System.Xml.Schema.XmlSchema xmlSchema = types.Schemas[0];

            foreach (object item in xmlSchema.Items)
            {
                System.Xml.Schema.XmlSchemaElement schemaElement = item as System.Xml.Schema.XmlSchemaElement;

                if (schemaElement != null)
                {
                    if (schemaElement.Name == messagePartName)
                    {
                        System.Xml.Schema.XmlSchemaType schemaType = schemaElement.SchemaType;
                        System.Xml.Schema.XmlSchemaComplexType complexType = schemaType as System.Xml.Schema.XmlSchemaComplexType;

                        if (complexType != null)
                        {
                            System.Xml.Schema.XmlSchemaParticle particle = complexType.Particle;
                            System.Xml.Schema.XmlSchemaSequence sequence = particle as System.Xml.Schema.XmlSchemaSequence;
                            if (sequence != null)
                            {
                                foreach (System.Xml.Schema.XmlSchemaElement childElement in sequence.Items)
                                {
                                    string parameterName = childElement.Name;
                                    string parameterType = childElement.SchemaTypeName.Name;
                                    parameters.Add(new Parameter(parameterName, parameterType));
                                }
                            }
                        }
                    }
                }
                else
                {
                    System.Xml.Schema.XmlSchemaComplexType complexType = item as System.Xml.Schema.XmlSchemaComplexType;

                    if (complexType != null)
                    {
                        System.Xml.Schema.XmlSchemaParticle particle = complexType.Particle;
                        System.Xml.Schema.XmlSchemaSequence sequence = particle as System.Xml.Schema.XmlSchemaSequence;

                        if (sequence != null)
                        {
                            foreach (System.Xml.Schema.XmlSchemaElement childElement in sequence.Items)
                            {
                                string parameterName = childElement.Name;
                                string parameterType = childElement.SchemaTypeName.Name;
                                parameters.Add(new Parameter(parameterName, parameterType));
                            }
                        }

                        System.Xml.Schema.XmlSchemaAll all = particle as System.Xml.Schema.XmlSchemaAll;

                        if (all != null)
                        {
                            foreach (System.Xml.Schema.XmlSchemaElement childElement in all.Items)
                            {
                                string parameterName = childElement.Name;
                                string parameterType = childElement.SchemaTypeName.Name;
                                parameters.Add(new Parameter(parameterName, parameterType));
                            }
                        }
                    }
                }
            }
            return parameters.ToArray();
        }

        /// <summary>
        /// WebMethodInfo
        /// </summary>
        public WebMethodInfoCollection WebMethods
        {
            get { return _webMethods; }
        }

        /// <summary>
        /// Url
        /// </summary>
        public Uri Url
        {
            get { return _url; }
            set { _url = value; }
        }
    }

    /// <summary>
    /// Information about a web service operation
    /// </summary>
    public class WebMethodInfo
    {
        string _name;
        Parameter[] _inputParameters;
        Parameter[] _outputParameters;

        /// <summary>
        /// OperationInfo
        /// </summary>
        public WebMethodInfo(string name, Parameter[] inputParameters, Parameter[] outputParameters)
        {
            _name = name;
            _inputParameters = inputParameters;
            _outputParameters = outputParameters;
        }

        /// <summary>
        /// Name
        /// </summary>
        public string Name
        {
            get { return _name; }
        }

        /// <summary>
        /// InputParameters
        /// </summary>
        public Parameter[] InputParameters
        {
            get { return _inputParameters; }
        }

        /// <summary>
        /// OutputParameters
        /// </summary>
        public Parameter[] OutputParameters
        {
            get { return _outputParameters; }
        }
    }

    /// <summary>
    /// A collection of WebMethodInfo objects
    /// </summary>
    public class WebMethodInfoCollection : KeyedCollection<string, WebMethodInfo>
    {
        /// <summary>
        /// Constructor
        /// </summary>
        public WebMethodInfoCollection() : base() { }

        protected override string GetKeyForItem(WebMethodInfo webMethodInfo)
        {
            return webMethodInfo.Name;
        }
    }

    /// <summary>
    /// represents a parameter (input or output) of a web method.
    /// </summary>
    public struct Parameter
    {
        /// <summary>
        /// constructor
        /// </summary>
        /// <param name="name">
        /// <param name="type">
        public Parameter(string name, string type)
        {
            this.Name = name;
            this.Type = type;
        }

        /// <summary>
        /// Name
        /// </summary>
        public string Name;
        /// <summary>
        /// Type
        /// </summary>
        public string Type;
    }
}